socket.io 4.4.1に更新したらサーバー側からsocket切断してもクライアント側が切断されなくなった
最終的な正しい実装
disconnectIfNotUserがnextを呼ばずに終了しているので、socket.io内のMapにTCP socketとsessionが残り続ける
以下は2022年1月の試行錯誤のログですshokai.icon
scrapboxのSocket.IOを4.1.2から4.4.1に更新しようとしている
https://github.com/nota/scrapbox/pull/5579
特殊なhttp requestが送られるとnode.jsプロセスがkillされるというEngine.IOの脆弱性の修正のため
この脆弱性の方はEngine.IOを6.1.1に更新すれば解消する
たぶん脆弱性対応の影響だと思うが、socket.io server middlewareでdisconnectした後の挙動が変わったようだshokai.icon
changelogにはそれらしい内容が記載されていない
https://github.com/socketio/socket.io/blob/master/CHANGELOG.md
https://github.com/socketio/engine.io/blob/master/CHANGELOG.md
サーバー側からの切断
scrapboxでは
接続してきたsocket.ioのhttp部分のrequestを見て、user sessionに紐づけて
ログインユーザーでは無い場合、socketを即座に切断している
code:server/sockets/middlewares/index.js
export const disconnectIfNotUser = (socket, next) => {
if (!socket.user) return socket.disconnect() // 切断
next()
}
socket.io server middlewareで実装している
こういう状態になる
サーバー側
socket.ioレイヤーでは切断されている
io.sockets.socketsからはいなくなっている
engien.ioレイヤーでは切断されていない
health apiのio.engine.clientsCount.toString()のカウントが減っていない事で確認できる
http://localhost:4000/api/health/sockets
クライアント側は
「自分は切断されていない」と認識しているようだ
切断されていないので、このままクライアント側トリガーのdisconnectが発生すると、自動的にreconnectしようとする
そしてreconnectは成功する
でもサーバー側ではsocket.ioレベルでは切断されていて、engine.ioレベルでは接続できている
なんだこの状態shokai.icon
https://github.com/nota/scrapbox/pull/5578#issuecomment-1013849889 でtestが通らないと書いている問題の原因
ログインユーザーのcookieを持たないsocket.io接続が即座に切断されなくなっている
上の現象は普通には再現できない
scrapboxのclient jsは、自分がログインユーザーである事を確認してからsocket.ioを接続しに行く
検証のためログインできていなくても接続するようにして、ローカルで試している
情報漏洩は発生しない
非ログインユーザーにproject member用のメッセージが飛んでしまう様な問題は発生しない
socket.ioのレイヤーで接続中のクライアントとして認識されていない
全ての通信はproject毎のsocket.io roomの中で配信されている
roomへの参加時にはproject memberかどうかチェックしている
dissconnectIfNotUserはmiddlewareとして読み込まれている
その時点では接続できていない
実際socket.connected: falseになっている
未接続のsocketを切断しようとしているのだから、socketの下のengine.ioについては何もしなくなっているのかもしれないshokai.icon
https://github.com/socketio/socket.io/blob/master/lib/socket.ts#L539-L540
その通りだった
code:js
public disconnect(close = false): this {
if (!this.connected) return this;
socket.ioだけでなくengine.ioも切断する
socket.connを使う
https://socket.io/docs/v4/server-api/#socketconn
code:js
export const disconnectIfNotUser = (socket, next) => {
if (!socket.user) {
socket.disconnect()
socket.conn.close() // これを追加
return
}
next()
}
これで解決したshokai.icon
socket.disconnect()もいらないな
実質何も実行されない関数なので
でもなんとなく残しておいた方が良い気がする
意図が伝わりやすい